using System;
using System.Collections;
using System.Text;

using Qiang.Topology.Algorithm;
using Qiang.Topology.Operation;

namespace Qiang.Topology.Geometries
{
    /// <summary>  
    /// Basic implementation of <c>LineString</c>.
    /// </summary>    
    public class LineString : Geometry ,ILineString
    {
        /// <summary>
        /// Represents an empty <c>LineString</c>.
        /// </summary>
        public static readonly ILineString Empty = new GeometryFactory().CreateLineString(new Coordinate[] { });

        /// <summary>  
        /// The points of this <c>LineString</c>.
        /// </summary>
        private ICoordinateSequence points;

        public override Coordinate[] Coordinates
        {
            get
            {
                return points.ToCoordinateArray();
            }
        }

        public virtual ICoordinateSequence CoordinateSequence
        {
            get
            {
                return points;
            }
        }

        /// <summary>
        /// Constructs a <c>LineString</c> with the given points.
        /// </summary>
        /// <param name="points">
        /// The points of the linestring, or <c>null</c>
        /// to create the empty point. This array must not contain <c>null</c>
        /// elements. Consecutive points may not be equal.
        /// </param>
        /// <param name="precisionModel">
        /// The specification of the grid of allowable points
        /// for this <c>LineString</c>.
        /// </param>
        /// <param name="SRID">
        /// The ID of the Spatial Reference System used by this
        /// <c>LineString</c>.
        /// </param>
        /// <deprecated> 
        /// Use GeometryFactory instead.
        /// </deprecated>
        [Obsolete("Use GeometryFactory instead")]
        public LineString(Coordinate[] points, PrecisionModel precisionModel, int SRID) 
            : this(DefaultCoordinateSequenceFactory.Instance.Create(points == null ? new Coordinate[] { } : points), 
                   new GeometryFactory(precisionModel, SRID, DefaultCoordinateSequenceFactory.Instance)) { }

        /// <param name="points">
        /// The points of the linestring, or <c>null</c>
        /// to create the empty point. Consecutive points may not be equal.
        /// </param>
        public LineString(ICoordinateSequence points, GeometryFactory factory) : base(factory)
        {            
            if (points == null) 
                points = factory.CoordinateSequenceFactory.Create(new Coordinate[] { });
            if (points.Count == 1)
                throw new ArgumentException("point array must contain 0 or >1 elements");
            this.points = points;
        }


        public LineString(Coordinate[] points)
            : this(new DefaultCoordinateSequence(points), DefaultGeometryFactory)
        {
        }

        public virtual Coordinate GetCoordinateN(int n) 
        {
            return points.GetCoordinate(n);
        }

        public override Coordinate Coordinate
        {
            get
            {
                if (IsEmpty) return null;
                return points.GetCoordinate(0);
            }
        }

        public override Dimensions Dimension 
        {
            get
            {
                return Dimensions.Curve;
            }
        }

        public override Dimensions BoundaryDimension
        {
            get
            {
                if (IsClosed)
                {
                    return Dimensions.False;
                }
                return Dimensions.Point;
            }
        }

        public override bool IsEmpty 
        {
            get
            {
                return points.Count == 0;
            }
        }

        public override int NumPoints
        {
            get
            {
                return points.Count;
            }
        }

        public virtual IPoint GetPointN(int n) 
        {
            return Factory.CreatePoint(points.GetCoordinate(n));
        }

        public virtual IPoint StartPoint 
        {
            get
            {
                if (IsEmpty)
                {
                    return null;
                }
                return GetPointN(0);
            }
        }

        public virtual IPoint EndPoint
        {
            get
            {
                if (IsEmpty)
                {
                    return null;
                }
                return GetPointN(NumPoints - 1);
            }
        }

        public virtual bool IsClosed
        {
            get
            {
                if (IsEmpty)
                {
                    return false;
                }
                return GetCoordinateN(0).Equals2D(GetCoordinateN(NumPoints - 1));
            }
        }

        public virtual bool IsRing
        {
            get
            {
                return IsClosed && IsSimple;
            }
        }

        [Obsolete("Use reflection! GetType().FullName")]
        public override string GeometryType
        {
            get
            {
                return "LineString";
            }
        }

        /// <summary>  
        /// Returns the length of this <c>LineString</c>
        /// </summary>
        /// <returns> 
        /// The length of the polygon
        /// </returns>
        public override double Length
        {
            get
            {
                return CGAlgorithms.Length(points);
            }
        }

        public override bool IsSimple
        {
            get
            {
                return (new IsSimpleOp()).IsSimple((LineString)ToInternalGeometry(this));
            }
        }

        public override IGeometry Boundary
        {
            get
            {
                if (IsEmpty)
                {
                    return Factory.CreateGeometryCollection(null);
                }
                if (IsClosed)
                {
                    return Factory.CreateMultiPoint((Coordinate[])null);
                }
                return Factory.CreateMultiPoint(new IPoint[] { StartPoint, EndPoint });
            }
        }

        /// <summary>
        /// Returns true if the given point is a vertex of this <c>LineString</c>.
        /// </summary>
        /// <param name="pt">
        /// The <c>Coordinate</c> to check.
        /// </param>
        /// <returns>
        /// <c>true</c> if <c>pt</c> is one of this <c>LineString</c>'s vertices.
        /// </returns>
        public virtual bool IsCoordinate(Coordinate pt) 
        {
            for (int i = 0; i < points.Count; i++) 
                if (points.GetCoordinate(i).Equals(pt))
                    return true;                
            return false;
        }

        protected override IEnvelope ComputeEnvelopeInternal() 
        {
            if (IsEmpty) 
                return new Envelope();

            //Convert to array, then access array directly, to avoid the function-call overhead
            //of calling #get millions of times. #toArray may be inefficient for
            //non-BasicCoordinateSequence CoordinateSequences. [Jon Aquino]
            Coordinate[] coordinates = points.ToCoordinateArray();
            double minx = coordinates[0].X;
            double miny = coordinates[0].Y;
            double maxx = coordinates[0].X;
            double maxy = coordinates[0].Y;
            //OptimizeIt shows that Math#min and Math#max here are a bottleneck.
            //Replace with direct comparisons. [Jon Aquino] 
            for (int i = 1; i < coordinates.Length; i++) 
            {
                minx = minx < coordinates[i].X ? minx : coordinates[i].X;
                maxx = maxx > coordinates[i].X ? maxx : coordinates[i].X;
                miny = miny < coordinates[i].Y ? miny : coordinates[i].Y;
                maxy = maxy > coordinates[i].Y ? maxy : coordinates[i].Y;
            }
            return new Envelope(minx, maxx, miny, maxy);
        }

        public override bool EqualsExact(IGeometry other, double tolerance) 
        {
            if (!IsEquivalentClass(other)) 
                return false;

            LineString otherLineString = (LineString)other;
            if (points.Count != otherLineString.points.Count)
                return false;

            for (int i = 0; i < points.Count; i++)
                if (!Equal(points.GetCoordinate(i), otherLineString.points.GetCoordinate(i), tolerance))             
                    return false;            
            return true;
        }

        public override void Apply(ICoordinateFilter filter) 
        {
            for (int i = 0; i < points.Count; i++)
                filter.Filter(points.GetCoordinate(i));
        }

        public override void Apply(IGeometryFilter filter) 
        {
            filter.Filter(this);
        }

        public override void Apply(IGeometryComponentFilter filter) 
        {
            filter.Filter(this);
        }

        public override object Clone()
        {
            LineString ls = (LineString) base.Clone();
            ls.points = (ICoordinateSequence)points.Clone();
            return ls;
        }

        /// <summary> 
        /// Normalizes a LineString.  A normalized linestring
        /// has the first point which is not equal to it's reflected point
        /// less than the reflected point.
        /// </summary>
        public override void Normalize()
        {
            for (int i = 0; i < points.Count / 2; i++) 
            {
                int j = points.Count - 1 - i;
                // skip equal points on both ends
                if (!points.GetCoordinate(i).Equals(points.GetCoordinate(j))) 
                {
                    if (points.GetCoordinate(i).CompareTo(points.GetCoordinate(j)) > 0) 
                        CoordinateArrays.Reverse(Coordinates);                    
                    return;
                }
            }
        }

        protected override bool IsEquivalentClass(IGeometry other) 
        {
            return other is ILineString;
        }

        protected internal override int CompareToSameClass(object o)
        {
            LineString line = o as LineString;
            // MD - optimized implementation
            int i = 0;
            int j = 0;
            while (i < points.Count && j < line.points.Count) 
            {
                int comparison = points.GetCoordinate(i).CompareTo(line.points.GetCoordinate(j));
                if (comparison != 0) 
                    return comparison;                
                i++;
                j++;
            }
            if (i < points.Count) 
                return 1;            
            if (j < line.points.Count)
                return -1;            
            return 0;            
        }
    }
}
